package com.example.tongyao.system.aop;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import eu.bitwalker.useragentutils.UserAgent;
import lombok.extern.log4j.Log4j2;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;


/**
 * Aop过滤类
 *
 * 说明：AOP就是在某一个类或方法执行前后打个标记，声明在执行到这里之前要先执行什么，执行完这里之后要接着执行什么。
 *
 * 这就对应着AOP的一些专业术语以及使用场景:
 *
 * Aspect（切面）： 声明类似于Java中的类声明，在Aspect中会包含一些Pointcut及相应的Advice。
 * Joint point（连接点）： 表示在程序中明确定义的点。包括方法的调用、对类成员的访问等。
 * Pointcut（切入点）： 表示一个组Joint point，如方法名、参数类型、返回类型等等。
 *
 * Advice（通知）： Advice定义了在Pointcut里面定义的程序点具体要做的操作，它通过(before、around、After： 在执行方法后调用Advice，after、return是方法正常返回后调用，after\throw是方法抛出异常后调用。
 * Before： 在执行方法前调用Advice，比如请求接口之前的登录验证。
 * Around： 在执行方法前后调用Advice，这是最常用的方法。
 * Finally： 方法调用后执行Advice，无论是否抛出异常还是正常返回。
 *
 *
 */

//@Aspect 告诉Spring这是个切面类
@Aspect

//@Compoment 将转换成Spring容器中的bean或者是代理bean
@Component
@Log4j2
public class LogAspect {

    private final Logger logger = LoggerFactory.getLogger(LogAspect.class);


    //定义切入点 就是需要拦截的切面
    @Pointcut("execution(public * cn.com.antMap.tree.controller.*.*(..))")
    public void controllerMethod() {}

    //定义切入点 就是需要拦截的切面
    /*private final String POINTCUT = "execution(* cn.com.antMap.tree.controller..*(..))";*/


    //进入方法时间戳(计时)
    private Long startTime;

    //方法结束时间戳(计时)
    private Long endTime;

    /**
     * 进入方法请求执行前
     *
     * @param joinPoint
     * @throws Exception
     */
    /*@Before("controllerMethod()")
    public void LogRequestInfo(JoinPoint joinPoint) {
        startTime = System.currentTimeMillis();

        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = ((ServletRequestAttributes)requestAttributes).getRequest();
        StringBuilder requestLog = new StringBuilder();
        Signature signature = joinPoint.getSignature();
        // 打印请求内容
        logger.info("===============请求内容开始===============");
        logger.info("请求地址:" + request.getRequestURL().toString());
        logger.info("请求IP:" + request.getRemoteAddr());
        logger.info("请求方式:" + request.getMethod());
        logger.info("请求类方法:" + joinPoint.getSignature());
        logger.info("请求类方法参数值:" + Arrays.toString(joinPoint.getArgs()));
        // 处理请求参数
        String[] paramNames = ((MethodSignature) signature).getParameterNames();
        Object[] paramValues = joinPoint.getArgs();
        int paramLength = null == paramNames ? 0 : paramNames.length;
        if (paramLength == 0) {
            requestLog.append("请求参数 = {} ");
        } else {
            requestLog.append("请求参数 = [");
            for (int i = 0; i < paramLength - 1; i++) {
                requestLog.append(paramNames[i]).append("=").append(JSONObject.toJSONString(paramValues[i])).append(",");
            }
            requestLog.append(paramNames[paramLength - 1]).append("=").append(JSONObject.toJSONString(paramValues[paramLength - 1])).append("]");
        }
        logger.info("请求参数明细:"+requestLog.toString());


        UserAgent userAgent = UserAgent.parseUserAgentString(request.getHeader("User-Agent"));
        // 打印请求的内容

        // 系统信息
        logger.info("浏览器 : {}", userAgent.getBrowser().toString());
        logger.info("浏览器版本 : {}", userAgent.getBrowserVersion());
        logger.info("操作系统: {}", userAgent.getOperatingSystem().toString());
        logger.info("===============请求内容结束===============");
    }*/


    /**
     * 进入方法请求执行后
     *
     * @param o
     * @throws Exception
     */
    /*@AfterReturning(returning = "o", pointcut = "controllerMethod()")
    public void logResultVOInfo(Object o){
        logger.info("--------------返回内容开始----------------");
        logger.info("Response内容:" + JSON.toJSONString(o));
        logger.info("--------------返回内容结束----------------");
    }*/


    /**
     * 返回通知 正常结束时进入此方法
     * @param ret
     */
    /*@AfterReturning(returning = "ret", pointcut = "controllerMethod()")
    public void doAfterReturning(Object ret) {
        endTime = System.currentTimeMillis();

        logger.info("请求结束时间 : {}", LocalDateTime.now());
        logger.info("请求耗时 : {}", (endTime - startTime));
        // 处理完请求，返回内容
        logger.info("请求返回 : {}", ret);
    }*/



    /**
     * 该切面发生异常信息时进行拦截
     * @param joinPoint
     * @param ex
     */
    @AfterThrowing(pointcut = "controllerMethod()", throwing = "ex")
    public void doAfterThrowing(JoinPoint joinPoint, Exception ex) {
        String methodName = joinPoint.getSignature().getName();
        List<Object> args = Arrays.asList(joinPoint.getArgs());
        System.out.println("连接点方法为：" + methodName + ",参数为：" + args + ",异常为：" + ex);

    }


    /**
     * 使用环绕通知，一下子全部输出
     * @param joinPoint
     * @throws Throwable
     */
    @Around("controllerMethod()")
    public void deAround(ProceedingJoinPoint joinPoint) throws Throwable {

        startTime = System.currentTimeMillis();

        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = ((ServletRequestAttributes)requestAttributes).getRequest();
        StringBuilder requestLog = new StringBuilder();
        Signature signature = joinPoint.getSignature();
        // 打印请求内容
        logger.info("===============请求内容开始===============");
        logger.info("请求地址:" + request.getRequestURL().toString());
        logger.info("请求IP:" + request.getRemoteAddr());
        logger.info("请求方式:" + request.getMethod());
        logger.info("请求类方法:" + joinPoint.getSignature());
        logger.info("请求类方法参数值:" + Arrays.toString(joinPoint.getArgs()));
        // 处理请求参数
        String[] paramNames = ((MethodSignature) signature).getParameterNames();
        Object[] paramValues = joinPoint.getArgs();
        int paramLength = null == paramNames ? 0 : paramNames.length;
        if (paramLength == 0) {
            requestLog.append("请求参数 = {} ");
        } else {
            requestLog.append("请求参数 = [");
            for (int i = 0; i < paramLength - 1; i++) {
                requestLog.append(paramNames[i]).append("=").append(JSONObject.toJSONString(paramValues[i])).append(",");
            }
            requestLog.append(paramNames[paramLength - 1]).append("=").append(JSONObject.toJSONString(paramValues[paramLength - 1])).append("]");
        }
        logger.info("请求参数明细:"+requestLog.toString());


        UserAgent userAgent = UserAgent.parseUserAgentString(request.getHeader("User-Agent"));
        // 打印请求的内容

        // 系统信息
        logger.info("浏览器 : {}", userAgent.getBrowser().toString());
        logger.info("浏览器版本 : {}", userAgent.getBrowserVersion());
        logger.info("操作系统: {}", userAgent.getOperatingSystem().toString());
        logger.info("===============请求内容结束===============");




        // joinPoint.proceed()：当我们执行完切面代码之后，还有继续处理业务相关的代码。proceed()方法会继续执行业务代码，并且其返回值，就是业务处理完成之后的返回值。
        Object o = joinPoint.proceed();
        logger.info("--------------返回内容开始----------------");
        logger.info("Response内容:" + JSON.toJSONString(o));
        logger.info("--------------返回内容结束----------------");

        endTime = System.currentTimeMillis();

        logger.info("请求结束时间 : {}", LocalDateTime.now());
        logger.info("请求耗时 : {}", (endTime - startTime));

    }


}